import { check, validationResult } from "express-validator";
// import validator from "validator";
import Ajv from "ajv";
import {
    isInteger, isString, isPlainObject, isBoolean, isNull, cloneDeep, isUndefined,
} from "lodash";
import {
    CHAINCODE_DEPLOY, CHAINCODE_INVOKE, CHAINCODE_SET_STATE, REQUEST_FAILURE,
} from "./const";

const ajv = new Ajv();

const codeLanguageTypes = ["CODE_JAVASCRIPT", "CODE_SCALA", "CODE_VCL_DLL", "CODE_VCL_EXE", "CODE_VCL_WASM", "CODE_WASM"];
const runTypes = ["RUN_SERIAL", "RUN_PARALLEL", "RUN_OPTIONAL"];
const stateTypes = ["STATE_BLOCK", "STATE_GLOBAL"];

// eslint-disable-next-line import/prefer-default-export
export const generateTxValidators = [
    check("txid").custom((value) => { // could be null, undefined, ""
        if (!isNull(value) && !isUndefined(value) && !isString(value)) {
            throw new Error(`The txid must be a string or null if provided, but got ${value}`);
        }
        return true;
    }),
    check("oid").custom((value) => { // could be null, undefined, ""
        if (!isNull() && !isUndefined(value) && !isString(value)) {
            throw new Error(`The oid must be a string or null if provided, but got ${value}`);
        }
        return true;
    }),
    check("gasLimit").custom((value) => { // could be null
        if (!isNull(value) && !isUndefined(value) && (!isInteger(value) || value < 0)) {
            throw new Error(`The gasLimit must be a no-negative integer or null if provided, but got ${value}`);
        }
        return true;
    }),
    check("txType").custom((value) => {
        const txTypeEnums = [
            CHAINCODE_DEPLOY,
            CHAINCODE_INVOKE,
            CHAINCODE_SET_STATE,
        ];
        if (!isNull(value) && !isUndefined(value) && txTypeEnums.indexOf(value) === -1) {
            throw new Error(`The txType must be a string whose value is one of ${txTypeEnums}, but got ${value}`);
        }
        return true;
    }),
    check("contractName").custom((value) => {
        if (!isNull(value) && !isUndefined(value) && !isString(value)) {
            throw new Error(`The contractName must be a string if provided, but got ${value}`);
        }
        return true;
    }),
    check("contractVersion").custom((value) => {
        if (!isNull(value) && !isUndefined(value) && (!isInteger(value) || value <= 0)) {
            throw new Error(`The contractVersion must be a positive number, but got ${value}`);
        }
        return true;
    }),
    check("contractFunction").custom((value) => {
        if (!isNull(value) && !isUndefined(value) && !isString(value)) throw new Error(`The contractFunction must be a string, but got ${value}`);
        return true;
    }),
    check("args").custom((value, { req }) => {
        if (isNull(value) || isUndefined(value)) {
            throw new Error("The args should not be null or undefined");
        }
        if (req.body.txType === CHAINCODE_DEPLOY) {
            if (!isPlainObject(value)) {
                throw new Error(`The args should be a json object when txType is ${CHAINCODE_DEPLOY}, but got type ${typeof value}`);
            }
            const schema = {
                type: "object",
                properties: {
                    timeout: { type: "integer", minimum: 0 },
                    codePackage: { type: "string" },
                    legalProse: { type: "string" },
                    codeLanguageType: { enum: codeLanguageTypes },
                    runType: { enum: runTypes },
                    stateType: { enum: stateTypes },
                    initParameters: { type: "string" },
                },
                required: ["codePackage", "codeLanguageType"],
                additionalProperties: false,
            };
            const validate = ajv.compile(schema);
            const valid = validate(value);
            if (!valid) {
                throw new Error(`Wrong schema for the args with errors: ${JSON.stringify(validate.errors)}`);
            }
            return true;
        }
        if (req.body.txType === CHAINCODE_INVOKE) {
            if (!isString(value)) {
                throw new Error(`The args should be a string value when txType is ${CHAINCODE_INVOKE}, but got type ${typeof value}`);
            }
            return true;
        }
        if (req.body.txType === CHAINCODE_SET_STATE) {
            if (!isBoolean(value)) {
                throw new Error(`The args should be a boolean value when txType is ${CHAINCODE_SET_STATE}, but got type ${typeof value}`);
            }
        }
        return true;
    }),
    (req, res, next) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            res.status(400).json({
                code: REQUEST_FAILURE, message: `Bad request, with error(s): ${JSON.stringify(errors.array())}`,
            });
        } else {
            next();
        }
    },
];

export const generateTxSanitizers = [
    check("args").customSanitizer((value, { req }) => {
        if (!value || req.body.txType !== CHAINCODE_DEPLOY) {
            return value;
        }
        const sanitizedArgs = cloneDeep(value);
        sanitizedArgs.codeLanguageType = codeLanguageTypes.indexOf(value.codeLanguageType) + 1;
        if (value.runType) {
            sanitizedArgs.runType = runTypes.indexOf(value.runType) + 1;
        }
        if (value.stateType) {
            sanitizedArgs.stateType = stateTypes.indexOf(value.stateType) + 1;
        }
        return sanitizedArgs;
    }),
];
